package lang.asm;

import editor.NotImplementedException;
import lang.Encoder;
import lang.Language;
import model.Segment;
import model.code.CodeFileChunk;
import model.code.Function;
import model.code.GlobalVariable;
import model.code.line.*;
import model.symbol.*;
import observer.ObservedString;
import util.BooleanList;
import util.ByteList;
import util.IntList;
import util.Pair;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * This class is used for encoding assembly language into machine code.
 *
 * @author Neil Dickson
 */
public class ASMEncode extends Encoder {
	public static final ASMEncode instance = new ASMEncode();

	public static final short	PLUS_R_START	= 0x100;
	public static final short	PLUS_R_END		= 0x200;
	public static final short	MEM_NUM_START	= 0x200;
	public static final short	MEM_NUM_END		= 0x208;
	public static final short	MEM_REG			= 0x209;
	public static final short	REG_MEM			= 0x20A;
	public static final short	IMM				= 0x20B;
	public static final short	REX				= 0x20C;
	public static final short	REX_OPTIONAL	= 0x20D;
	public static final short	DOUBLE_REG		= 0x20E;
	public static final short	REX_W			= 0x20F;
	public static final short	JCC_SHORT		= 0x210;
	public static final short	JMP_SHORT		= 0x211;
	public static final short	JMP_SHORT_ONLY	= 0x212;
	public static final short	JMP_NEAR		= 0x213;

	public static final byte	REX_START		= 0x40;
	public static final byte	W_BIT			= 0x08;
	public static final byte	R_BIT			= 0x04;
	public static final byte	X_BIT			= 0x02;
	public static final byte	B_BIT			= 0x01;

	public static final byte	MOD_REG_REG		= (byte)0xC0;

	public static final short	M_PLUS_RA_START		= 0x100;
	public static final short	M_PLUS_RA_END		= 0x200;
	public static final short	M_PLUS_RA_RB8_START	= 0x200;
	public static final short	M_PLUS_RA_RB8_END	= 0x300;
	public static final short	M_PLUS_RA8_START	= 0x300;
	public static final short	M_PLUS_RA8_END		= 0x400;
	public static final short	M_IMM8				= 0x1000;
	public static final short	M_IMM16				= 0x1001;
	public static final short	M_IMM32				= 0x1002;

	public static final Register	BP	= (Register)ASM.language.getScope().get("bp").get(0);
	public static final Register	EBP	= (Register)ASM.language.getScope().get("ebp").get(0);
	public static final Register	RBP	= (Register)ASM.language.getScope().get("rbp").get(0);

	public static final	byte[]	SEGMENT_REGISTER_TO_PREFIX	= {/*ES*/0x26,/*CS*/0x2E,/*SS*/0x36,/*DS*/0x3E,/*FS*/0x64,/*GS*/0x65};

	private ASMEncode() {

	}

	/**
	 * Encodes the specified GlobalVariable if possible
	 * @param variable the GlobalVariable to encode
	 * @param errors the list of errors as they are discovered.  NOTE: This must be synchronized-on before use, because multiple threads add to it.
	 * @return the encoded data, or possibly null if a fatal error occurred
	 */
	protected EncodedData encode(GlobalVariable variable, ArrayList<SyntaxError> errors) {
		Segment segment = variable.getSegment();
		boolean isUninit = segment.getType()==Segment.UNINIT_DATA;
		EncodedData data = new EncodedData();
		data.code = variable;
		data.segmentIndex = variable.getProject().getSegments().indexOf(segment);
		data.symbolOffsets = new ArrayList<SymbolOffset>(1);
		data.symbolOffsets.add(new SymbolOffset(variable.getID(),0));
		ByteList encoded = isUninit ? null : new ByteList(variable.getPieces().size()*8);
		ArrayList<Relocation> relocations = isUninit ? null : new ArrayList<Relocation>(1);
		handleOffsetAndAlignment(variable.getOffset(),variable.getAlignment().get(),data,false);

		Scope globalScope = variable.getGlobalScope();
		int mode = variable.getMode();
		Language language = variable.getLanguage();
		for (GlobalVariable.Piece piece : variable.getPieces()) {
			encodeData(data, encoded, relocations, globalScope, mode, piece.getType().toString(), language, piece.getInitialValue(), piece.getType(), 0, errors);
		}

		if (relocations!=null && !relocations.isEmpty()) {
			relocations.trimToSize();
			data.relocations = relocations;
		}
		if (encoded!=null && !encoded.isEmpty()) {
			encoded.trimToSize();
			data.encoded = encoded;
		}
		return data;
	}

	private void encodeData(EncodedData data,ByteList encoded,ArrayList<Relocation> relocations,Scope globalScope,int mode,String typeString,Language language,Line valueLine,Line typeLine,int valueStartIndex,ArrayList<SyntaxError> errors) {
		Scope languageScope = language.getScope();
		DataType type = DataTypeUtil.getDataType(typeString,languageScope,globalScope,mode);
		if (type==null) {
			if (typeLine.getNumErrors()==0) {
				typeLine.addError(new SyntaxError(typeLine,0,typeLine.length(),"This doesn't resolve to a valid type"));
			}
			synchronized (errors) {
				errors.add(typeLine.getErrors()[0]);
			}
		}
		else {
			// TODO: Produce an error if there is initialized data in an unintialized data segment.
			Object sizeOrError = language.getParser().evaluateArray(valueLine,type,encoded,relocations,valueStartIndex,valueLine.getNumReferences());
			if (sizeOrError==null || sizeOrError instanceof SyntaxError) {
				// NOTE: An error will always be returned if the value line had at least one error.
				if (sizeOrError==null) {
					Object[] refs = valueLine.getReferences();
					for (int i=0;i<valueLine.getNumReferences();++i) {
						if (refs[0]==null) {
							sizeOrError = new SyntaxError(valueLine,valueLine.getTokenColumn(i),valueLine.getTokenColumn(i+1),valueLine.toString(valueLine.getTokenColumn(i),valueLine.getTokenColumn(i+1))+" isn't defined in this context.");
							break;
						}
					}
					if (sizeOrError==null) {
						sizeOrError = new SyntaxError(typeLine,0,typeLine.length(),"This doesn't evaluate to a valid value (or sequence of values) of type "+typeString);
					}
					valueLine.addError((SyntaxError)sizeOrError);
				}
				synchronized (errors) {
					errors.add((SyntaxError)sizeOrError);
				}
			}
			else { //sizeOrError instanceof Integer
				data.size += (Integer)sizeOrError;
			}
		}
	}

	/**
	 * Adds any specified offset or alignment to the data, handling errors
	 * @param offset the offset text
	 * @param alignment the alignment, or {@link CodeFileChunk#DEFAULT_ALIGNMENT_MARKER} if default
	 * @param data to be filled with the offset and alignment data
	 * @param isFunction true if Function; false if GlobalVariable
	 * @return an error if offset wasn't a valid constant integer with no relocations
	 */
	public SyntaxError handleOffsetAndAlignment(ValueLine offset,int alignment,EncodedData data,boolean isFunction) {
		if (offset.length()!=0) {
			Object offsetObject = offset.evaluate();
			if (offsetObject!=null && offsetObject instanceof SyntaxError) {
				return (SyntaxError)offsetObject;
			}
			else if (offsetObject==null) {
				return new SyntaxError(offset,0,offset.length(),"An error occurred evaluating this specified offset.");
			}
			else if (((Immediate)offsetObject).relocation!=null || ((Immediate)offsetObject).diffRelocation!=null) {
				return new SyntaxError(offset,0,offset.length(),"Sorry, having an offset depend on another offset isn't supported yet,\nand it's a very low priority unless many people ask for it.");
			}
			else if (((Immediate)offsetObject).isDouble) {
				return new SyntaxError(offset,0,offset.length(),"Sorry, offsets can't be floating-point values, only integers.");
			}
			else {
				data.offset = ((Immediate)offsetObject).constant;
			}
		}
		else if (alignment!=CodeFileChunk.DEFAULT_ALIGNMENT_MARKER) {
			data.alignment = alignment;
		}
		else {
			data.alignment = isFunction ? CodeFileChunk.DEFAULT_FUNCTION_ALIGNMENT : CodeFileChunk.DEFAULT_VARIABLE_ALIGNMENT;
		}
		return null;
	}

	/**
	 * Encodes the specified Function if possible
	 * @param function the Function to encode
	 * @param errors the list of errors as they are discovered.  NOTE: This must be synchronized-on before use, because multiple threads add to it.
	 * @return the encoded data, or possibly null if a fatal error occurred
	 */
	public EncodedData encode(Function function,HashMap<Long,Function> importSet,ArrayList<SyntaxError> errors) {
		ObservedString callingConvention = function.getCallingConvention();
		// Don't encode inline functions here; they'll be remade when their parameters have values
		if (callingConvention.equals(Function.INLINE_TEXT)) {
			EncodedData data = new EncodedData();
			// NOTE: size is already 0 and encoded is already null
			data.symbolOffsets = new ArrayList<SymbolOffset>(0);
			return data;
		}
		Segment segment = function.getSegment();
		if (segment.getType()==Segment.UNINIT_DATA) {
			synchronized (errors) {
				errors.add(new SyntaxError(function.getName(),0,0,"This function is in an uninitialized data segment, which would mean that it wouldn't appear in the output file."));
			}
		}
		EncodedData data = new EncodedData();
		data.code = function;
		data.segmentIndex = function.getProject().getSegments().indexOf(segment);
		LineList<? extends Line> lines = function.getCode();
		ByteList encoded = new ByteList(lines.size()*8);
		ArrayList<Relocation> relocations = new ArrayList<Relocation>(lines.size());
		data.symbolOffsets = new ArrayList<SymbolOffset>(1+(lines.size()>>2));
		HashMap<LocalVariable,Integer> localVarOffsets = new HashMap<LocalVariable, Integer>(function.getParameters().size()+function.getLocalVariables().size());
		handleOffsetAndAlignment(function.getOffset(),function.getAlignment().get(),data,true);

		data.symbolOffsets.add(new SymbolOffset(function.getID(),encoded.size()));
		int mode = function.getMode();
		Scope languageScope = ASM.language.getScope();
		Scope globalScope = function.getGlobalScope();

		boolean specialRet = false;
		short paramSize = 0;
		int localVarSize = 0;
		Register localVarReg;
		// TODO: Add support for other calling conventions.
		if (callingConvention.length()==0 || callingConvention.equals(Function.STANDARD_TEXT)) {
			if (mode==64) {
				localVarReg = RBP;
			}
			else if (mode==32) {
				localVarReg = EBP;
			}
			else { // mode==16
				localVarReg = BP;
			}

			if (!function.getParameters().isEmpty() || !function.getLocalVariables().isEmpty()) {
				for (LocalVariable var : function.getParameters()) {
					DataType dataType = DataTypeUtil.getDataType(var.getType().toString(),languageScope,globalScope,mode);
					// NOTE: Stack has bp/ebp/rbp at +0, return address at +(mode/8), and first parameter at +(mode/4).
					if (dataType!=ASM.REGISTER) {
						localVarOffsets.put(var,paramSize+(mode>>>2));
						if (dataType!=null) {
							paramSize += dataType.getSize();
						}
						else {
							SyntaxError error;
							if (var.getType().getNumErrors()>0) {
								error = var.getType().getErrors()[0];
							}
							else {
								error = new SyntaxError(var.getType(),0,var.getType().length(),"This doesn't resolve to a valid data type.");
							}
							synchronized (errors) {
								errors.add(error);
							}
							// Assume a dataType size of the number of bytes in a pointer in case code below will go weird without this.
							paramSize += (mode>>>3);
						}
					}
				}
				for (LocalVariable var : function.getLocalVariables()) {
					DataType dataType = DataTypeUtil.getDataType(var.getType().toString(),languageScope,globalScope,mode);
					if (dataType!=null) {
						localVarSize += dataType.getSize();
					}
					else {
						SyntaxError error;
						if (var.getType().getNumErrors()>0) {
							error = var.getType().getErrors()[0];
						}
						else {
							error = new SyntaxError(var.getType(),0,var.getType().length(),"This doesn't resolve to a valid data type.");
						}
						synchronized (errors) {
							errors.add(error);
						}
						// Assume a dataType size of the number of bytes in a pointer in case code below will go weird without this.
						localVarSize += (mode>>>3);
					}
				}
				int tempLocalVarSize = 0;
				for (LocalVariable var : function.getLocalVariables()) {
					DataType dataType = DataTypeUtil.getDataType(var.getType().toString(),languageScope,globalScope,mode);
					// NOTE: Local variables have negative offsets from bp/ebp/rbp
					localVarOffsets.put(var,tempLocalVarSize-localVarSize);
					tempLocalVarSize += dataType.getSize();
				}

				if (localVarSize>0 || paramSize>0) {
					specialRet = true;
					encoded.add((byte)0x55);	// push bp/ebp/rbp
					if (mode==64) {
						encoded.add((byte)(REX_START+W_BIT));
					}
					encoded.add((byte)0x8B);	// mov
					encoded.add((byte)(MOD_REG_REG+(5<<3)+4));	// bp,sp / ebp,esp / rbp,rsp (after prefix)
					if (localVarSize>0) {
						if (mode==64) {
							encoded.add((byte)(REX_START+W_BIT));
						}
						if (localVarSize<=128) {
							encoded.add((byte)0x83);	// add/or/adc/sbb/and/sub/xor/cmp rm,imm
							encoded.add((byte)(MOD_REG_REG+(0<<3)+4));	// add sp/esp/rsp (after prefix)
							encoded.add((byte)-localVarSize);
						}
						else {
							encoded.add((byte)0x81);	// add/or/adc/sbb/and/sub/xor/cmp rm,imm
							encoded.add((byte)(MOD_REG_REG+(0<<3)+4));	// add sp/esp/rsp (after prefix)
							if (mode==16) {
								encoded.addShort((short)-localVarSize);
							}
							else {
								encoded.addInt(-localVarSize);
							}
						}
					}
				}
			}
		}
		else {
			synchronized (errors) {
				// FIXME: This should be an error on function.getCallingConvention(), but that's not a LineList yet!!!
				errors.add(new SyntaxError(function.getName(),0,function.getName().length(),"Only the standard calling convention is supported at the moment, and you've selected the \""+callingConvention+"\" calling convention on the function "+function+"."));
			}
			// NOTE: This one returns here, because the calling convention not being resolved makes any references to local variables below invalid.
			return data;
		}

		// Keep track of the conditional assembly options
		int numIfs = 0;
		int numTopTrueIfs = 0;
		BooleanList hasPrecedingIf = new BooleanList();
		BooleanList elseIfSatisfied = new BooleanList();
		IntList openIfLines = new IntList(2);

		for (int lineNum = 0; lineNum<lines.size(); lineNum++) {
			Line aLine = lines.get(lineNum);
			if (!(aLine instanceof ASMLine)) {
				throw new NotImplementedException("I'm not sure what to do with a non-ASM line in an ASM function.");
			}
			ASMLine line = (ASMLine)aLine;
			if (line.hasLabel()) {
				data.symbolOffsets.add(new SymbolOffset(line.getLabel().getID(), encoded.size()));
			}
			if (line.getNumErrors()>0) {
				if (numIfs==numTopTrueIfs) {
					synchronized (errors) {
						errors.add(line.getErrors()[0]);
					}
				}
			}
			else if (line.getPrimaryKeyword()>=0 && line.getReferences()[line.getPrimaryKeyword()]!=null) {
				int primaryKeyword = line.getPrimaryKeyword();
				if (line.getReferences()[primaryKeyword] instanceof Mnemonic) {
					if (numIfs==numTopTrueIfs) {
						Mnemonic mnemonic = (Mnemonic)line.getReferences()[primaryKeyword];
						ArrayList<Object> operands = line.getOperands();
						if (specialRet && mnemonic.getName().equals("ret") && operands.isEmpty()) {
							if (callingConvention.length()==0 || callingConvention.equals(Function.STANDARD_TEXT)) {
								if (localVarSize>0) {
									if (mode==64) {
										encoded.add((byte)(REX_START+W_BIT));
									}
									if (localVarSize<=128) {
										encoded.add((byte)0x83);	// add/or/adc/sbb/and/sub/xor/cmp rm,imm
										encoded.add((byte)(MOD_REG_REG+(5<<3)+4));	// sub sp/esp/rsp (after prefix)
										encoded.add((byte)-localVarSize);
									}
									else {
										encoded.add((byte)0x81);	// add/or/adc/sbb/and/sub/xor/cmp rm,imm
										encoded.add((byte)(MOD_REG_REG+(5<<3)+4));	// sub sp/esp/rsp (after prefix)
										if (mode==16) {
											encoded.addShort((short)-localVarSize);
										}
										else {
											encoded.addInt(-localVarSize);
										}
									}
								}
								if (mode==64) {
									encoded.add((byte)(REX_START + W_BIT));
								}
								encoded.add((byte)0x5D);	// pop bp/ebp/rbp
								if (paramSize==0) {
									encoded.add((byte)0xC3);
								}
								else {
									encoded.add((byte)0xC2);
									encoded.addShort(paramSize);
								}
							}
							else {
								throw new NotImplementedException("Only the standard calling convention is supported at the moment, and you've selected the \"" + callingConvention + "\" calling convention on the function " + function + ".");
							}
						}
						else if (mnemonic.getName().equals("call") && operands.size()==1 && operands.get(0) instanceof Function && ((Function)operands.get(0)).isImported()) {
							Function calledFunction = (Function)operands.get(0);
							int relocationOffset = encodeImportCall(encoded,calledFunction,mode,importSet);
							relocations.add(new Relocation(line,RELOC_NORMAL,relocationOffset,calledFunction.getID()));
						}
						else {
							Pair<Object[], short[]> typesAndEncoding = line.getOperandTypesAndEncoding(operands, mode);
							encodeInstruction(line,mnemonic,operands,typesAndEncoding,encoded,relocations,mode,localVarReg,localVarOffsets,line.getOperandIndices(),errors);
						}
					}
				}
				else if (line.getReferences()[primaryKeyword] == ASM.INVOKE) {
					if (numIfs==numTopTrueIfs) {
						ArrayList<Object> operands = line.getOperands();
						if (operands.size()<1 || !(operands.get(0) instanceof Function)) {
							int startIndex = line.getTokenColumn(primaryKeyword+1);
							synchronized (errors) {
								errors.add(new SyntaxError(line,startIndex,startIndex,"Invoke is used for calling a function, so it needs to be followed by a function's name and its parameters."));
							}
						}
						else {
							Function calledFunction = (Function)operands.get(0);
							ObservedString calledConvention = calledFunction.getCallingConvention();
							if ((callingConvention.length()==0 || callingConvention.equals(Function.STANDARD_TEXT))
									&& (calledConvention.length()==0 || calledConvention.equals(Function.STANDARD_TEXT))) {
								int numRealParameters = 0;
								for (LocalVariable param : calledFunction.getParameters()) {
									if (!param.getType().equals(Function.AUTOMATIC_PARAM_TYPE_TEXT)) {
										++numRealParameters;
									}
								}
								if (operands.size()!=numRealParameters+1) {
									synchronized (errors) {
										int endCol = line.getTokenColumn(line.getOperandsEndIndex());
										int startCol = operands.size()<=calledFunction.getParameters().size() ? endCol : line.getTokenColumn(line.getOperandIndices().get(calledFunction.getParameters().size()));
										errors.add(new SyntaxError(line,startCol,endCol,"You've given "+(operands.size()-1)+" parameters, and this function needs "+calledFunction.getParameters().size()+" parameters."));
									}
								}
								else {
									// Find the info about "push", because we'll need it.
									ArrayList<Symbol> symbols = languageScope.get("push");
									Mnemonic push = null;
									for (Symbol symbol : symbols) {
										if (symbol instanceof Mnemonic) {
											push = (Mnemonic)symbol;
											break;
										}
									}
									ArrayList<Object[]> pushOperandTypes = push.getOperandTypes(mode);
									// This is used to hold the operand for each push when calling encodeInstruction.
									ArrayList<Object> pushOperands = new ArrayList<Object>(1);
									pushOperands.add(null);
									Pair<Object[],short[]> typesAndEncoding = new Pair<Object[],short[]>(null,null);
									IntList operandIndices = new IntList(2);
									operandIndices.add(0);
									operandIndices.add(0);
									// Push parameters from last to first
									int opIndex = numRealParameters-1;
									for (int i=calledFunction.getParameters().size()-1;i>=0;--i) {
										Object operand = operands.get(opIndex+1);
										TypeLine typeLine = calledFunction.getParameters().get(i).getType();
										DataType paramType = DataTypeUtil.getDataType(typeLine.toString().trim(),languageScope,globalScope,mode);
										if (paramType!=null && !typeLine.equals(Function.AUTOMATIC_PARAM_TYPE_TEXT)) {
											int opType = DataTypeUtil.getOperandType(operand,mode);
											int opTypeSize = DataTypeUtil.TYPE_MAP[opType].size>>3;
											if (paramType.getSize()==opTypeSize || (operand instanceof Immediate && paramType.getSize()>opTypeSize)) {
												int typesIndex;
												for (typesIndex = 0; typesIndex<pushOperandTypes.size(); typesIndex++) {
													Object[] types = pushOperandTypes.get(typesIndex);
													if (types.length==1 && ((types[0] instanceof Integer && DataTypeUtil.isCompatibleType(opType, (Integer)types[0])) || (types[0] instanceof String && operand.toString().equals(types[0])))) {
														break;
													}
												}
												operandIndices.set(0,line.getOperandIndices().get(opIndex+1));
												operandIndices.set(1,(opIndex+2<operands.size()) ? line.getOperandIndices().get(opIndex+2)-1 : line.getOperandsEndIndex());
												if (typesIndex<pushOperandTypes.size()) {
													pushOperands.set(0,operand);
													typesAndEncoding.a = pushOperandTypes.get(typesIndex);
													typesAndEncoding.b = push.getEncodings(mode).get(typesIndex);
													encodeInstruction(line,push,pushOperands,typesAndEncoding,encoded,relocations,mode,localVarReg,localVarOffsets,operandIndices,errors);
												}
												else {
													int startCol = line.getTokenColumn(operandIndices.get(0));
													int endCol = line.getTokenColumn(operandIndices.get(1));
													synchronized (errors) {
														errors.add(new SyntaxError(line,startCol,endCol,"This parameter ("+line.toString(startCol,endCol)+((operand instanceof Immediate)?"="+((Immediate)operand).constant:"")+") can't be pushed onto the stack, since \"push\" won't accept it as an operand."));
													}
												}
											}
											else {
												int startCol = line.getTokenColumn(line.getOperandIndices().get(opIndex+1));
												int endCol = line.getTokenColumn((line.getOperandIndices().size()>opIndex+2) ? line.getOperandIndices().get(opIndex+2)-1 : line.getOperandsEndIndex());
												synchronized (errors) {
													errors.add(new SyntaxError(line,startCol,endCol,"This parameter needs to be "+paramType.getSize()+" bytes large, but it's "+opTypeSize+" bytes large."));
												}
											}
											--opIndex;
										}
										else if (paramType==null) {
											int startCol = line.getTokenColumn(line.getOperandIndices().get(opIndex+1));
											int endCol = line.getTokenColumn((line.getOperandIndices().size()>opIndex+2) ? line.getOperandIndices().get(opIndex+2)-1 : line.getOperandsEndIndex());
											synchronized (errors) {
												errors.add(new SyntaxError(line,startCol,endCol,"This parameter doesn't have a valid type in its function's definition ("+typeLine+")."));
											}
											--opIndex;
										}
									}
									if (calledFunction.isImported()) {
										int relocationOffset = encodeImportCall(encoded,calledFunction,mode,importSet);
										relocations.add(new Relocation(line,RELOC_NORMAL,relocationOffset,calledFunction.getID()));
									}
									else {
										int relocationOffset = encodeCall(encoded,mode);
										relocations.add(new Relocation(line,RELOC_REL,relocationOffset,calledFunction.getID()));
									}
								}
							}
							else if (calledConvention.equals(Function.INLINE_TEXT)) {
								Function inline = calledFunction.copy(function.getParent());
								int opIndex = 1;
								for (int i=0;i<inline.getParameters().size();++i) {
									LocalVariable param = inline.getParameters().get(i);
									TypeLine typeLine = param.getType();
									if (typeLine.length()==0 || typeLine.toString().toLowerCase().equals(Function.REQUIRED_MACRO_PARAM_TEXT)) {
										// NOTE: May have less parameters given values than specified in the function definition, in which case, they should be ""
										int startIndex = opIndex>=line.getOperandIndices().size() ? line.getOperandsEndIndex() : line.getOperandIndices().get(opIndex);
										int endIndex = opIndex+1>=line.getOperandIndices().size() ? line.getOperandsEndIndex() : line.getOperandIndices().get(opIndex+1)-1;
										String paramText = line.toString(line.getTokenColumn(startIndex),line.getTokenColumn(endIndex));
										// Replace all references to the parameter with the paramText provided in the call
										for (Line inlineLine : inline.getCode()) {
											for (int j=0;j<inlineLine.getNumReferences();++j) {
												if (inlineLine.getReferences()[j]==param) {
													int numReferences = inlineLine.getNumReferences();
													inlineLine.replace(inlineLine.getReferenceIndices()[j],j+1<numReferences ? inlineLine.getReferenceIndices()[j+1] : inlineLine.length(),paramText);
													// Skip over any extra references created by this replacement so that no infinite replacement loops occur.
													j += Math.max(inlineLine.getNumReferences()-numReferences,0);
												}
											}
										}
										inline.getParameters().remove(i);
										--i;
										++opIndex;
									}
									else if (typeLine.equals(Function.AUTOMATIC_PARAM_TYPE_TEXT)) {
										inline.getParameters().remove(i);
										--i;
									}
									else {
										synchronized (errors) {
											errors.add(new SyntaxError(typeLine,0,typeLine.length(),"Sorry, regular parameters aren't yet supported for inline functions."));
										}
										// NOTE: Once supported, these parameters probably won't be removed.
										inline.getParameters().remove(i);
										--i;
										++opIndex;
									}
								}
								if (!inline.getLocalVariables().isEmpty()) {
									synchronized (errors) {
										errors.add(new SyntaxError(inline.getLocalVariables().get(0).getName(),0,inline.getLocalVariables().get(0).getName().length(),"Sorry, local variables aren't yet supported for inline functions."));
									}
									inline.getLocalVariables().clear();
								}
								// Now that parameters have been dealt with, set the calling convention to none and encode the function
								inline.getCallingConvention().setValue(Function.NO_CALLING_CONVENTION_TEXT);
								ArrayList<SyntaxError> inlineErrors = new ArrayList<SyntaxError>();
								EncodedData inlineData = encode(inline,importSet,inlineErrors);
								if (inlineData.encoded!=null) {
									if (inlineData.relocations!=null) {
										for (Relocation rel : inlineData.relocations) {
											rel.offset += encoded.size();
										}
										relocations.addAll(inlineData.relocations);
									}
									for (SymbolOffset so : inlineData.symbolOffsets) {
										so.offset += encoded.size();
									}
									data.symbolOffsets.addAll(inlineData.symbolOffsets);
									encoded.addAll(inlineData.encoded);
								}
								for (SyntaxError error : inlineErrors) {
									int index;
									if (error.getLine()!=null && (index = inline.getCode().indexOf(error.getLine()))>=0) {
										error.setDescription("When called from "+function+"("+lineNum+"), "+error.getDescription());
										error.setLine(calledFunction.getCode().get(index));
									}
								}
								synchronized (errors) {
									errors.addAll(inlineErrors);
								}
							}
							else {
								int operandIndex = line.getOperandIndices().get(0);
								synchronized (errors) {
									errors.add(new SyntaxError(line,line.getTokenColumn(operandIndex),line.getTokenColumn(operandIndex+1),"The calling convention of this function ("+calledConvention+") isn't supported yet."));
								}
							}
						}
					}
				}
				else if (line.getReferences()[primaryKeyword] instanceof KeywordDataType) {
					if (numIfs==numTopTrueIfs) {
						KeywordDataType type = (KeywordDataType)line.getReferences()[primaryKeyword];
						encodeData(data, encoded, relocations, globalScope, mode, type.toString(), ASM.language, line, line, primaryKeyword+1, errors);
					}
				}
				else if (line.getReferences()[primaryKeyword] == ASM.IF) {
					int start = primaryKeyword+1;
					while (start<line.getNumReferences() && line.getReferences()[start]==Line.WHITE_SPACE_MARKER) {
						++start;
					}
					if (start==line.getNumReferences()) {
						synchronized (errors) {
							errors.add(new SyntaxError(line,line.length(),line.length(),"This IF has no condition on it."));
						}
						if (numTopTrueIfs==numIfs) {
							++numTopTrueIfs;
						}
					}
					else if (numTopTrueIfs==numIfs) {
						int end = line.hasComment() ? line.getCommentStart() : line.getNumReferences();
						Object immediateOrError = ASMParse.instance.immediateShunting(line,start,end);
						if (immediateOrError!=null && immediateOrError instanceof Immediate) {
							Immediate immediate = (Immediate)immediateOrError;
							if (!immediate.isZero()) {
								++numTopTrueIfs;
							}
						}
						else {
							synchronized (errors) {
								if (immediateOrError instanceof SyntaxError) {
									errors.add((SyntaxError)immediateOrError);
								}
								else {
									errors.add(new SyntaxError(line,line.getTokenColumn(start),line.getTokenColumn(end),"This IF condition doesn't evaluate to a valid value."));
								}
							}
							++numTopTrueIfs;
						}
					}
					++numIfs;
					// If an "elseif" line could change whether code is included or not,
					// mark down whether this condition satisfied the if-elseif-elseif-... chain or not.
					if (numTopTrueIfs>=numIfs-1) {
						elseIfSatisfied.add(numTopTrueIfs==numIfs);
					}
					hasPrecedingIf.add(true);
					openIfLines.add(lineNum);
				}
				else if (line.getReferences()[primaryKeyword] == ASM.ELSEIF) {
					if (!hasPrecedingIf.getLast()) {
						// Add the error, but make it look like there is an "if" that was false so that we don't get tons of errors.
						synchronized (errors) {
							errors.add(new SyntaxError(line,line.getTokenColumn(primaryKeyword),line.getTokenColumn(primaryKeyword+1),"There's no IF to match this ELSEIF."));
						}
						++numIfs;
						if (numTopTrueIfs==numIfs-1) {
							elseIfSatisfied.add(false);
						}
						hasPrecedingIf.add(true);
						openIfLines.add(lineNum);
					}
					int start = primaryKeyword+1;
					while (start<line.getNumReferences() && line.getReferences()[start]==Line.WHITE_SPACE_MARKER) {
						++start;
					}
					if (start==line.getNumReferences()) {
						synchronized (errors) {
							errors.add(new SyntaxError(line,line.length(),line.length(),"This ELSEIF has no condition on it."));
						}
						if (numTopTrueIfs==numIfs-1 && !elseIfSatisfied.getLast()) {
							++numTopTrueIfs;
						}
						else if (numTopTrueIfs==numIfs) {
							--numTopTrueIfs;
						}
					}
					else if (numTopTrueIfs==numIfs) {
						--numTopTrueIfs;
					}
					else if (numTopTrueIfs==numIfs-1 && !elseIfSatisfied.getLast()) {
						int end = line.hasComment() ? line.getCommentStart() : line.getNumReferences();
						Object immediateOrError = ASMParse.instance.immediateShunting(line,start,end);
						if (immediateOrError!=null && immediateOrError instanceof Immediate) {
							Immediate immediate = (Immediate)immediateOrError;
							if (!immediate.isZero()) {
								++numTopTrueIfs;
							}
						}
						else {
							synchronized (errors) {
								if (immediateOrError instanceof SyntaxError) {
									errors.add((SyntaxError)immediateOrError);
								}
								else {
									errors.add(new SyntaxError(line,line.getTokenColumn(start),line.getTokenColumn(end),"This IF condition doesn't evaluate to a valid value."));
								}
							}
							++numTopTrueIfs;
						}
					}
					// If this "elseif" condition satisfied the if-elseif-elseif-... chain or not, mark it down
					if (numTopTrueIfs==numIfs) {
						elseIfSatisfied.set(elseIfSatisfied.size()-1,true);
					}
					openIfLines.set(openIfLines.size()-1,lineNum);
				}
				else if (line.getReferences()[primaryKeyword] == ASM.ELSE) {
					if (!hasPrecedingIf.getLast()) {
						// Add the error, but make it look like there is an "if" that was false so that we don't get tons of errors.
						synchronized (errors) {
							errors.add(new SyntaxError(line,line.getTokenColumn(primaryKeyword),line.getTokenColumn(primaryKeyword+1),"There's no IF to match this ELSE."));
						}
						++numIfs;
						if (numTopTrueIfs==numIfs-1) {
							elseIfSatisfied.add(false);
						}
						hasPrecedingIf.add(true);
						openIfLines.add(lineNum);
					}
					if (numTopTrueIfs==numIfs) {
						--numTopTrueIfs;
					}
					else if (numTopTrueIfs==numIfs-1 && !elseIfSatisfied.get(numIfs-1)) {
						++numTopTrueIfs;
					}
					hasPrecedingIf.set(hasPrecedingIf.size()-1,false);
					openIfLines.set(openIfLines.size()-1,lineNum);
				}
				else if (line.getReferences()[primaryKeyword] == ASM.ENDIF) {
					if (numIfs==0) {
						synchronized (errors) {
							errors.add(new SyntaxError(line,line.getTokenColumn(primaryKeyword),line.getTokenColumn(primaryKeyword+1),"There's no IF to match this ENDIF."));
						}
					}
					else {
						if (numTopTrueIfs==numIfs) {
							--numTopTrueIfs;
						}
						--numIfs;
						if (elseIfSatisfied.size()>numIfs) {
							elseIfSatisfied.removeLast();
						}
						if (hasPrecedingIf.size()>numIfs) {
							hasPrecedingIf.removeLast();
						}
						openIfLines.removeLast();
					}
				}
				else if (line.getReferences()[primaryKeyword] == ASM.ERR) {
					if (numTopTrueIfs==numIfs) {
						String text = "Error specified explicitly with \".err\"";
						for (int i = 0; i<line.getNumReferences(); i++) {
							if (line.getReferences()[i]==Line.STRING_LITERAL_MARKER) {
								text = line.toString(line.getTokenColumn(i)+1,line.getTokenColumn(i+1)-1);
								break;
							}
						}
						synchronized (errors) {
							errors.add(new SyntaxError(line,line.getTokenColumn(primaryKeyword),line.length(),text));
						}
					}
				}
			}
		}
		if (numIfs>0) {
			Line line = lines.get(openIfLines.getLast());
			synchronized (errors) {
				errors.add(new SyntaxError(line,0,line.length(),"This IF has no associated ENDIF."));
			}
		}

		if (!relocations.isEmpty()) {
			relocations.trimToSize();
			data.relocations = relocations;
		}
		if (!encoded.isEmpty()) {
			encoded.trimToSize();
			data.encoded = encoded;
			data.size = encoded.size();
		}
		data.symbolOffsets.trimToSize();
		return data;
	}

	public void encodeInstruction(ASMLine line, Mnemonic mnemonic, ArrayList<Object> operands, Pair<Object[], short[]> typesAndEncoding, ByteList encoded, ArrayList<Relocation> relocations, int mode, Register localVarReg, HashMap<LocalVariable, Integer> localVarOffsets, IntList operandIndices, ArrayList<SyntaxError> errors) {
		Object[] operandTypes = typesAndEncoding.a;
		short[] encodingPattern = typesAndEncoding.b;
		int immOpIndex = 0;
		int rexOffset = -1;
		int instructionStart = encoded.size();
		boolean rexInserted = false;
		if (line.hasPrefix()) {
			encoded.add((byte)line.getPrefix().getEncodings(32).get(0)[0]);
		}
		if (line.hasSegmentPrefix()) {
			int reg = line.getSegmentPrefix().getNumber();
			if (reg>=0 && reg<SEGMENT_REGISTER_TO_PREFIX.length) {
				encoded.add(SEGMENT_REGISTER_TO_PREFIX[reg]);
			}
		}
		for (short s : encodingPattern) {
			if (s>=0 && s<0x100) {
				encoded.add((byte)s);
			}
			else if (s>=PLUS_R_START && s<PLUS_R_END) {
				boolean found = false;
				for (int i = 0; i<operandTypes.length; ++i) {
					Object o = operandTypes[i];
					Object operand = operands.get(i);
					// NOTE: Must check for register type for handling cases like st0,stx where the 2nd operand is the right one.
					if (operand instanceof Register && o instanceof Integer && DataTypeUtil.containsRegisterType((Integer)o)) {
						int reg = ((Register)operand).getNumber();
						encoded.add((byte)(s - PLUS_R_START + (reg&7)));
						if (reg>7) {
							if (rexOffset<0) {
								System.err.println("rexOffset isn't set for "+line);
								rexOffset = instructionStart;
							}
							if (rexInserted) {
								encoded.set(rexOffset, (byte)(B_BIT + encoded.get(rexOffset)));
							}
							else {
								encoded.add(rexOffset, (byte)(REX_START + B_BIT));
								rexInserted = true;
							}
						}
						found = true;
						break;
					}
				}
				if (!found) {
					throw new NotImplementedException("I messed up, sorry.\nThere's a " + mnemonic + " instruction with no register and no error on it.\nPlease let me know about this on the forum at http://www.codecortex.com/forum/ and it'll get fixed fast.");
				}
			}
			else if (s==IMM) {
				while (immOpIndex<operandTypes.length && !((operandTypes[immOpIndex] instanceof Integer) && (DataTypeUtil.isImmediateType((Integer)operandTypes[immOpIndex]) || DataTypeUtil.isFunctionType((Integer)operandTypes[immOpIndex])))) {
					++immOpIndex;
				}
				if (immOpIndex>=operandTypes.length) {
					int lineNum = (line.getList()!=null) ? line.getList().indexOf(line) : -1;
					CodeFileChunk function = (line.getList()!=null) ? line.getList().getParent() : null;
					throw new NotImplementedException("I messed up, sorry.\nThere's a " + mnemonic + " instruction"+((lineNum>=0) ? " on line "+lineNum+" of "+function : "")+" with not enough immediates and no error on it.\nPlease let me know about this on the forum at http://www.codecortex.com/forum/ and it'll get fixed fast.");
				}
				int immSize = DataTypeUtil.TYPE_MAP[(Integer)operandTypes[immOpIndex]].size;
				Object imm = operands.get(immOpIndex);
				if (imm instanceof Immediate) {
					Immediate immediate = (Immediate)imm;
					if (immediate.relocation!=null) {
						immediate.relocation.offset = encoded.size();
						relocations.add(immediate.relocation);
					}
					if (immediate.diffRelocation!=null) {
						immediate.diffRelocation.offset = encoded.size();
						relocations.add(immediate.diffRelocation);
					}
					long value = immediate.constant;
					if (immSize==8) {
						encoded.add((byte)value);
					}
					else if (immSize==16) {
						encoded.addShort((short)value);
					}
					else if (immSize==32) {
						if (!immediate.isDouble) {
							encoded.addInt((int)value);
						}
						else {
							encoded.addInt(Float.floatToIntBits((float)Double.longBitsToDouble(value)));
						}
					}
					else if (immSize==64) {
						encoded.addLong(value);
					}
					else {
						throw new NotImplementedException("I messed up, sorry.\nOperand type #" + operandTypes[immOpIndex] + " (" + DataTypeUtil.TYPE_MAP[(Integer)operandTypes[immOpIndex]].name + ") somehow has a size that isn't 8,16,32, or 64 bits.\nPlease let me know about this on the forum at http://www.codecortex.com/forum/ and it'll get fixed fast.");
					}
				}
				else if (imm instanceof Function) {
					Relocation relocation = new Relocation(line,RELOC_NORMAL,encoded.size(),((Function)imm).getID());
					relocations.add(relocation);
					if (immSize==16 || immSize==32 || immSize==64) {
						encoded.addZeros((immSize==16) ? 2 : 4);
					}
					else {
						throw new NotImplementedException("I messed up, sorry.\nOperand type #" + operandTypes[immOpIndex] + " (" + DataTypeUtil.TYPE_MAP[(Integer)operandTypes[immOpIndex]].name + ") somehow has a size that isn't 16,32, or 64 bits.\nPlease let me know about this on the forum at http://www.codecortex.com/forum/ and it'll get fixed fast.");
					}
				}
				else {
					throw new NotImplementedException("I messed up, sorry.\nThere's a " + mnemonic + " instruction with a floating-point immediate value and no error on it.\nPlease let me know about this on the forum at http://www.codecortex.com/forum/ and it'll get fixed fast.");
				}
				++immOpIndex;
			}
			else if (s==MEM_REG || s==REG_MEM || s==DOUBLE_REG || (s>=MEM_NUM_START && s<MEM_NUM_END)) {
				int reg;
				int memReg = -1;
				if (s==REG_MEM || s==DOUBLE_REG) {
					reg = ((Register)operands.get(0)).getNumber();
					if (s==DOUBLE_REG) {
						memReg = reg;
					}
					else if (operands.get(1) instanceof Register) {
						memReg = ((Register)operands.get(1)).getNumber();
					}
				}
				else if (s==MEM_REG) {
					reg = ((Register)operands.get(1)).getNumber();
					if (operands.get(0) instanceof Register) {
						memReg = ((Register)operands.get(0)).getNumber();
					}
				}
				else { // s>=MEM_NUM_START && s<MEM_NUM_END
					reg = s - MEM_NUM_START;
					if (operands.get(0) instanceof Register) {
						memReg = ((Register)operands.get(0)).getNumber();
					}
				}

				if (memReg!=-1) {
					// Put in mod-reg-r/m byte for reg,reg
					encoded.add((byte)(MOD_REG_REG|((reg&7)<<3)|(memReg&7)));
					// Handle REX prefix
					if (reg>7 || memReg>7) {
						if (rexOffset<0) {
							System.err.println("rexOffset isn't set for "+line);
							rexOffset = instructionStart;
						}
						if (rexInserted) {
							encoded.set(rexOffset, (byte)(((reg>7) ? R_BIT : 0) + ((memReg>7) ? B_BIT : 0) + encoded.get(rexOffset)));
						}
						else {
							encoded.add(rexOffset, (byte)(REX_START + ((reg>7) ? R_BIT : 0) + ((memReg>7) ? B_BIT : 0)));
							rexInserted = true;
						}
					}
				}
				else {	// NOTE: Will never be this case in DOUBLE_REG case
					int operandNumber = (s==REG_MEM) ? 1 : 0;
					MemoryOperand memory = (MemoryOperand)operands.get(operandNumber);
					boolean isTranslationSuccess = true;
					if (memory.localVariable!=null) {
						isTranslationSuccess = memory.translateVariable(localVarReg, localVarOffsets.get(memory.localVariable));
						if (!isTranslationSuccess) {
							int operandStart = operandIndices.get(operandNumber);
							int operandEnd = (operandNumber+1<operandIndices.size()) ? operandIndices.get(operandNumber+1) : line.getOperandsEndIndex();
							SyntaxError error = new SyntaxError(line,line.getTokenColumn(operandStart),line.getTokenColumn(operandEnd),"This memory operand already has 2 registers in it, and the local variable would add a 3rd, which isn't supported.");
							line.addError(error);
							synchronized (errors) {
								errors.add(error);
							}
						}
					}
					if (isTranslationSuccess) {
						short[] memEncodingPattern = memory.findMatchEncoding(mode);
						if (memEncodingPattern==null) {
							int operandStart = operandIndices.get(operandNumber);
							int operandEnd = (operandNumber+1<operandIndices.size()) ? operandIndices.get(operandNumber+1) : line.getOperandsEndIndex();
							SyntaxError error = new SyntaxError(line,line.getTokenColumn(operandStart),line.getTokenColumn(operandEnd),"This memory operand has no valid encoding.  Note that "+((mode==64)?"rsp":"esp")+" can't be multiplied by a constant or added to itself"+((memory.localVariable!=null && memory.localVariable instanceof LocalVariable)?", and the local variable adds "+localVarReg+".":"."));
							line.addError(error);
							synchronized (errors) {
								errors.add(error);
							}
						}
						else {
							if (reg>7) {
								if (rexOffset<0) {
									System.err.println("rexOffset isn't set for "+line);
									rexOffset = instructionStart;
								}
								if (rexInserted) {
									encoded.set(rexOffset, (byte)(R_BIT + encoded.get(rexOffset)));
								}
								else {
									encoded.add(rexOffset, (byte)(REX_START + R_BIT));
									rexInserted = true;
								}
							}
							for (int i = 0; i<memEncodingPattern.length; i++) {
								short m = memEncodingPattern[i];
								int regPart = (i==0 && reg>=0) ? ((reg&7)<<3) : 0;
								if (m<0x100) {
									encoded.add((byte)(m + regPart));
								}
								else if (m==M_IMM8) {
									Immediate immediate = memory.offset;
									if (immediate!=null) {
										encoded.add((byte)immediate.constant);
									}
									else {
										encoded.add((byte)0);
									}
								}
								else if (m==M_IMM16 || m==M_IMM32) {
									Immediate immediate = memory.offset;
									if (immediate!=null) {
										if (immediate.relocation!=null) {
											immediate.relocation.offset = encoded.size();
											relocations.add(immediate.relocation);
										}
										if (immediate.diffRelocation!=null) {
											immediate.diffRelocation.offset = encoded.size();
											relocations.add(immediate.diffRelocation);
										}
										if (m==M_IMM16) {
											encoded.addShort((short)immediate.constant);
										}
										else { // m==M_IMM32
											encoded.addInt((int)immediate.constant);
										}
									}
									else if (m==M_IMM16) {
										encoded.addShort((short)0);
									}
									else { // m==M_IMM32
										encoded.addInt(0);
									}
								}
								else if (m>=M_PLUS_RA_START && m<M_PLUS_RA_END) {
									int base = memory.scaledIndexReg.getNumber();
									encoded.add((byte)((base&7) + m - M_PLUS_RA_START + regPart));
									if (base>7) {
										if (rexOffset<0) {
											System.err.println("rexOffset isn't set for "+line);
											rexOffset = instructionStart;
										}
										if (rexInserted) {
											encoded.set(rexOffset, (byte)(B_BIT + encoded.get(rexOffset)));
										}
										else {
											encoded.add(rexOffset, (byte)(REX_START + B_BIT));
											rexInserted = true;
										}
									}
								}
								else if (m>=M_PLUS_RA_RB8_START && m<M_PLUS_RA_RB8_END) {
									int base = memory.regularIndexReg.getNumber();
									int index = memory.scaledIndexReg.getNumber();
									encoded.add((byte)((base&7) + ((index&7)<<3) + m - M_PLUS_RA_RB8_START));
									if (base>7 || index>7) {
										if (rexOffset<0) {
											System.err.println("rexOffset isn't set for "+line);
											rexOffset = instructionStart;
										}
										if (rexInserted) {
											encoded.set(rexOffset, (byte)(((base>7) ? B_BIT : 0) + ((index>7) ? X_BIT : 0) + encoded.get(rexOffset)));
										}
										else {
											encoded.add(rexOffset, (byte)(REX_START + ((base>7) ? B_BIT : 0) + ((index>7) ? X_BIT : 0)));
											rexInserted = true;
										}
									}
								}
								else if (m>=M_PLUS_RA8_START && m<M_PLUS_RA8_END) {
									int index = memory.scaledIndexReg.getNumber();
									if (index==4) {
										index = memory.regularIndexReg.getNumber();
									}
									encoded.add((byte)(((index&7)<<3) + m - M_PLUS_RA8_START));
									if (index>7) {
										if (rexOffset<0) {
											System.err.println("rexOffset isn't set for "+line);
											rexOffset = instructionStart;
										}
										if (rexInserted) {
											encoded.set(rexOffset, (byte)(X_BIT + encoded.get(rexOffset)));
										}
										else {
											encoded.add(rexOffset, (byte)(REX_START + X_BIT));
											rexInserted = true;
										}
									}
								}
							}
						}
					}
				}
			}
			else if (s==REX_W) {
				rexOffset = encoded.size();
				encoded.add((byte)(REX_START + W_BIT));
				rexInserted = true;
			}
			else if (s==REX) {
				rexOffset = encoded.size();
				encoded.add(REX_START);
				rexInserted = true;
			}
			else if (s==REX_OPTIONAL) {
				rexOffset = encoded.size();
			}
			else if (s==JCC_SHORT || s==JMP_SHORT || s==JMP_SHORT_ONLY || s==JMP_NEAR) {
				Symbol label = (Symbol)operands.get(0);
				// Handle "jmp $" by jumping back to the start of the instruction (note that "jcxz" and "jecxz" may not be 2 bytes)
				// Also note that any branch prediction prefix won't matter the 2nd time around.
				if (label==ASM.HERE && (s==JCC_SHORT || s==JMP_SHORT || s==JMP_SHORT_ONLY)) {
					encoded.add((byte)(0x100-(encoded.size()+1-instructionStart)));
				}
				else {
					int relocType = RELOC_JCC_SHORT;
					if (s==JMP_SHORT) {
						relocType = RELOC_JMP_SHORT;
					}
					else if (s==JMP_SHORT_ONLY) {
						relocType = RELOC_JMP_SHORT_ONLY;
					}
					else if (s==JMP_NEAR) {
						relocType = RELOC_REL;
					}
					relocations.add(new Relocation(line,relocType, encoded.size(), label.getID()));
					if (s!=JMP_NEAR) {
						encoded.add((byte)0);
					}
					else if (mode!=16) {
						encoded.addInt(0);
					}
					else {
						encoded.addShort((short)0);
					}
				}
			}
		}
	}

	private int encodeImportCall(ByteList encoded, Function function, int mode, HashMap<Long,Function> importSet) {
		int relocationOffset = encoded.size()+2;
		if (mode==64) {
			encoded.add((byte)(REX_START+W_BIT));
			encoded.add((byte)0xFF);	// call rm64
			encoded.add((byte)0x14);	// [imm32s]
			encoded.add((byte)0x25);	//
			encoded.addInt(0);			// zero until resolving relocations
			relocationOffset += 2;		// 4 bytes before relocation instead of 2 bytes
		}
		else if (mode==32) {
			encoded.add((byte)0xFF);	// call rm32
			encoded.add((byte)0x15);	// [imm32]
			encoded.addInt(0);			// zero until resolving relocations
		}
		else { // mode==16
			encoded.add((byte)0xFF);	// call rm16
			encoded.add((byte)0x16);	// [imm16]
			encoded.addShort((short)0);	// zero until resolving relocations
		}
		synchronized (importSet) {
			if (!importSet.containsKey(function.getID())) {
				importSet.put(function.getID(), function);
			}
		}
		return relocationOffset;
	}

	private int encodeCall(ByteList encoded,int mode) {
		int relocationOffset = encoded.size()+1;
		encoded.add((byte)0xE8);		// call function
		if (mode!=16) {
			encoded.addInt(0);			// 32-bit relative address
		}
		else {
			encoded.addShort((short)0);	// 16-bit relative address
		}
		return relocationOffset;
	}
}
